package com.cx.auth.filter;

import com.cx.auth.AuthConstant;
import com.cx.auth.UserTokenService;
import com.cx.common.exception.BaseException;
import com.cx.common.restful.ResultCode;
import com.cx.common.spring.SpringBeanUtils;
import com.cx.common.utils.JsonUtils;
import com.cx.po.SysUser;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义JWT认证过滤器
 * 访问时校验token 并且把jwt转成security认识的对象
 * 该类继承自BasicAuthenticationFilter,顶级为OncePerRequestFilter
 * 在doFilterInternal方法中,
 * 从http请求头的Authorization 项读取token数据，然后用Jwt包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 *
 * @Author:chenxiaoyi
 * @Date:2020/12/31 16:45
 */
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {


    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 拦截所有的请求 一次
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        String header = request.getHeader(AuthConstant.AUTHORIZATION);

        if(header == null || !header.startsWith(AuthConstant.BEARER)){
            //token未获取到 则直接放行,此时如果请求的资源需要登录 则在 request里找不到认证信息 一样会被拦截
            chain.doFilter(request,response);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = this.getAuthentication(request, response);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        super.doFilterInternal(request, response, chain);
    }

    //解析token并转换成security认识的对象 设置到请求Context中
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response){
        String token = request.getHeader(AuthConstant.AUTHORIZATION);

        try {
            //解析token 解析失败抓住异常  解析成功说明有效
            String s = token.replaceAll(AuthConstant.BEARER, "");
            Claims claims = Jwts.parser().setSigningKey(AuthConstant.SIGNING_KEY)
                    .parseClaimsJws(s).getBody();
            SysUser sysUser = JsonUtils.string2Object(JsonUtils.object2String(claims.get(AuthConstant.AUTH_KEY)), SysUser.class);
            //token颁发时间
            long issudTime = claims.getIssuedAt().getTime();
            //token过期时间
            long expireTime = claims.getExpiration().getTime();

            long currentTimeMillis = System.currentTimeMillis();
            /*
            *  验证token时间有效性
            * 当前时间 > 颁发时间 + ((过期时间 - 颁发时间) / 2) &&  当前时间 < 过期时间 刷新token
            */
            if( issudTime + ((expireTime - issudTime )/2) < currentTimeMillis && currentTimeMillis < expireTime ){
                UserTokenService<SysUser> userTokenService = SpringBeanUtils.getBean(UserTokenService.class);
                String refreshToken = userTokenService.createAccessToken(sysUser);
                response.addHeader("refreshToken",refreshToken);
            }
            return new UsernamePasswordAuthenticationToken(sysUser,null,sysUser.getAuthorities());

        }catch (ExpiredJwtException e){
            log.error("Token 过期:{}",e.getMessage());
            throw new BaseException(ResultCode.TOKEN_IS_EXPIRED);
        }catch (UnsupportedJwtException e) {
            logger.error("Token格式错误: {} " + e);
            throw new BaseException("Token格式错误");
        } catch (MalformedJwtException e) {
            logger.error("Token没有被正确构造: {} " + e);
            throw new BaseException("Token没有被正确构造");
        } catch (SignatureException e) {
            logger.error("签名失败: {} " + e);
            throw new BaseException("签名失败");
        } catch (IllegalArgumentException e) {
            logger.error("非法参数异常: {} " + e);
            throw new BaseException("非法参数异常");
        }


    }









}
